//Библиотека Qt.
#include <QtGui>


//Внутренняя библиотека.
#include "mainwindow.h"
#include "devmanager.h"
#include "devmanagedialog.h"
#include "viddevice.h"
#include "openglwidget.h"
#include "xvideowidget.h"


//Объявление глобальных констант.
#define APP_VERSION "0.1.6"


//===========================================================================//
//=Заголовок: MainWindow::MainWindow() - public
//=Параметры:
//=Описание: Конструктор.
//===========================================================================//
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
logoLabel = NULL; dragVidWdg = NULL; oneWindowMode = false;

//Установить заголовок окна.
setWindowTitle(QString("EsperXM2 - ") + QString(APP_VERSION));

//Создать рабочую область главного окна приложения.
mainWidget = new QWidget(this);
setCentralWidget(mainWidget);

//Создать панель состояния.
QStatusBar *statusbar = new QStatusBar(this);
setStatusBar(statusbar);

//Создать менеджер устройств.
devManager = new DevManager(this);
connect(devManager, SIGNAL(information(const QString &)), this, SLOT(slotInformationShow(const QString &)));
connect(devManager, SIGNAL(warning(const QString &)), this, SLOT(slotWarningShow(const QString &)));
connect(devManager, SIGNAL(critical(const QString &)), this, SLOT(slotCriticalShow(const QString &)));
connect(devManager, SIGNAL(devicesConnect(const QList<VidDevice *> &)), this, SLOT(slotDevicesConnect(const QList<VidDevice *> &)), Qt::DirectConnection);
connect(devManager, SIGNAL(devicesDisconnect(const QList<int> &)), this, SLOT(slotDevicesDisconnect(const QList<int> &)), Qt::DirectConnection);

//Получить версию приложения.
QSettings settings;
QString appverstr = settings.value("APP_VERSION", "").toString();

//Если текущая версия приложения не соответствует сохранённой в конфигурационном файле...
if(!appverstr.isEmpty() && appverstr != QString(APP_VERSION))
	{
	QString msg = tr("Текущая версия EsperXM2 не соответствует настройкам в конфигурационном файле. Выполнить чтение настроек?");
	QMessageBox::StandardButton but = QMessageBox::question(this, tr("Запрос на подтверждение"), msg, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
	if(but == QMessageBox::Yes)
		{
		settings.setValue("APP_VERSION", QString(APP_VERSION));
		appverstr = QString(APP_VERSION);
	}
	else appverstr.clear();
}

//Если строка с версией приложения пуста...
if(appverstr.isEmpty())
	{
	showLogo();

	//Показать диалог настроек приложения.
	DevManageDialog *devmngdlg = new DevManageDialog(devManager, this);
	devmngdlg->setWindowModality(Qt::ApplicationModal);
	devmngdlg->setAttribute(Qt::WA_DeleteOnClose, true);
	connect(devmngdlg, SIGNAL(information(const QString &)), this, SLOT(slotInformationShow(const QString &)));
	connect(devmngdlg, SIGNAL(warning(const QString &)), this, SLOT(slotWarningShow(const QString &)));
	connect(devmngdlg, SIGNAL(critical(const QString &)), this, SLOT(slotCriticalShow(const QString &)));
	connect(devmngdlg, SIGNAL(vidWinTypeChange(const QString &)), this, SLOT(slotVidWinTypeChange(const QString &)));
	devmngdlg->show();
	return;
}

//Прочитать настройки приложения из конфигурационного файла.
else devManager->loadConfiguration(settings.childGroups());
}


//===========================================================================//
//=Заголовок: void MainWindow::keyPressEvent() - protected
//=Параметры:
//=Описание: Событие нажатия клавиши.
//===========================================================================//
void MainWindow::keyPressEvent(QKeyEvent *event)
{
//Если нажата <Esc>, выйти из программы.
if(event->key() == Qt::Key_Escape)
	{
	//Сохранить настройки приложения.
	devManager->saveConfiguration();

	//Выйти из программы.
	close(); event->accept(); return;
}

//Если нажаты <Ctrl> + <A>, показать диалог управления видеоустройствами.
else if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_A)
	{
	//Выбрать все устройства.
	devManager->selectAllDevices();

	//Перейти в режим просмотра.
	devManager->setMotionEnabled(false);

	//Показать диалог настроек приложения.
	DevManageDialog *devmngdlg = new DevManageDialog(devManager, this);
	devmngdlg->setWindowModality(Qt::ApplicationModal);
	devmngdlg->setAttribute(Qt::WA_DeleteOnClose, true);
	connect(devmngdlg, SIGNAL(information(const QString &)), this, SLOT(slotInformationShow(const QString &)));
	connect(devmngdlg, SIGNAL(warning(const QString &)), this, SLOT(slotWarningShow(const QString &)));
	connect(devmngdlg, SIGNAL(critical(const QString &)), this, SLOT(slotCriticalShow(const QString &)));
	connect(devmngdlg, SIGNAL(vidWinTypeChange(const QString &)), this, SLOT(slotVidWinTypeChange(const QString &)));
	devmngdlg->show();

	event->accept(); return;
}

//Если нажаты <Ctrl> + <V>, выполнить безусловный переход в режим просмотра.
else if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_V)
	{
	//Выбрать все устройства.
	devManager->selectAllDevices();

	//Перейти в режим просмотра.
	devManager->setMotionEnabled(false);

	event->accept(); return;
}

//Если нажаты <Ctrl> + <M>, выполнить безусловный переход в режим ДД.
else if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_M)
	{
	//Выбрать все устройства.
	devManager->selectAllDevices();

	//Перейти в режим ДД.
	devManager->setMotionEnabled(true);

	event->accept(); return;
}

//Если нажаты <Ctrl> + <S>, выполнить безусловное переключение
//активности записи видео в файл.
else if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_S)
	{
	//Выбрать все устройства.
	devManager->selectAllDevices();

	//Включить запись видео в файл.
	devManager->setFileWriterTrigger(true);

	event->accept(); return;
}

//Если нажаты <Alt> + <S>, выполнить небезусловное переключение
//активности записи видео в файл.
else if(event->modifiers() == Qt::AltModifier && event->key() == Qt::Key_S)
	{
	//Выбрать все устройства.
	devManager->selectAllDevices();

	//Отключить запись видео в файл.
	devManager->setFileWriterTrigger(false);

	event->accept(); return;
}

//Если нажаты <Ctrl> + <F>, сменить размер окна приложения.
else if(event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_F)
	{
	if(isFullScreen()) showNormal();
	else showFullScreen();
	event->accept(); return;
}

event->ignore();
}


//===========================================================================//
//=Заголовок: void MainWindow::mouseDoubleClickEvent() - protected
//=Параметры:
//=Описание: Событие двойного щелчка кнопки мыши.
//===========================================================================//
void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
{
if(event->button() != Qt::LeftButton) {event->ignore(); return;}
if(getVideoWindowsList().size() <= 1) {event->accept(); return;}

QWidget *widget = childAt(event->pos());
if(!widget) {event->accept(); return;}

QVariant property = widget->property("VidWidget");
if(!property.isValid()) {event->accept(); return;}
if(!property.toBool()) {event->accept(); return;}

VidWidget *vidwdg = (VidWidget *)widget;

oneWindowMode = !oneWindowMode;

hideVideoWindows();

switch(oneWindowMode)
	{
	case true: {
		QLayout *layout = mainWidget->layout();
		if(!layout) {event->accept(); return;}
		delete layout;

		vidwdg->show();

		QGridLayout *newlayout = new QGridLayout(mainWidget);
		newlayout->setMargin(1); newlayout->setSpacing(1);
		newlayout->addWidget(vidwdg, 0, 0, 1, 1);
	} break;

	case false: showVideoWindows(); break;
}

event->accept();
}


//===========================================================================//
//=Заголовок: void MainWindow::mousePressEvent() - protected
//=Параметры:
//=Описание: Событие нажатия кнопки мыши.
//===========================================================================//
void MainWindow::mousePressEvent(QMouseEvent *event)
{
if(event->button() != Qt::LeftButton) {event->ignore(); return;}
if(oneWindowMode) {event->accept(); return;}
if(getVideoWindowsList().size() <= 1) {event->accept(); return;}

QWidget *widget = childAt(event->pos());
if(!widget) {event->accept(); return;}

QVariant property = widget->property("VidWidget");
if(!property.isValid()) {event->accept(); return;}
if(!property.toBool()) {event->accept(); return;}

dragVidWdg = (VidWidget *)widget;

event->accept();
}


//===========================================================================//
//=Заголовок: void MainWindow::mouseReleaseEvent() - protected
//=Параметры:
//=Описание: Событие отпускания кнопки мыши.
//===========================================================================//
void MainWindow::mouseReleaseEvent(QMouseEvent *event)
{
if(event->button() != Qt::LeftButton) {event->ignore(); return;}
if(!dragVidWdg) {event->accept(); return;}

QWidget *widget = childAt(event->pos());
if(!widget) {dragVidWdg = NULL; event->accept(); return;}

QVariant property = widget->property("VidWidget");
if(!property.isValid()) {dragVidWdg = NULL; event->accept(); return;}
if(!property.toBool()) {dragVidWdg = NULL; event->accept(); return;}

VidWidget *vidwdg = (VidWidget *)widget;
VidDevice *viddev = vidwdg->getDevice();
if(!viddev) {dragVidWdg = NULL; event->accept(); return;}

VidDevice *dragviddev = dragVidWdg->getDevice();
if(!dragviddev) {dragVidWdg = NULL; event->accept(); return;}

dragVidWdg->connectVideoDevice(NULL);
vidwdg->connectVideoDevice(NULL);

dragVidWdg->connectVideoDevice(viddev);
vidwdg->connectVideoDevice(dragviddev);

dragVidWdg = NULL;

event->accept();
}


//===========================================================================//
//=Заголовок: void MainWindow::getVideoWindowsList() - private
//=Параметры:
//=Описание: Функция возврата списка видеоокон.
//===========================================================================//
QList<VidWidget *> MainWindow::getVideoWindowsList()
{
QObjectList objlist = mainWidget->children();

QList<VidWidget *> vidwdglist;

QObject *obj = NULL;
foreach(obj, objlist)
	{
	if(!obj) continue;
	VidWidget *vidwdg = qobject_cast<VidWidget *>(obj);
	if(vidwdg) vidwdglist.append(vidwdg);
}

return vidwdglist;
}


//===========================================================================//
//=Заголовок: void MainWindow::showVideoWindows() - private
//=Параметры:
//=Описание: Функция вывода видеоокон.
//===========================================================================//
void MainWindow::showVideoWindows()
{
//Удалить логотип, если существует.
if(logoLabel) {delete logoLabel; logoLabel = NULL;}

//Удалить старый менеджер размещения.
QLayout *oldlayout = mainWidget->layout();
if(oldlayout) delete oldlayout;

repaint();

//Создать новый менеджер размещения.
QGridLayout *layout = new QGridLayout(mainWidget);
layout->setMargin(1); layout->setSpacing(1);

QList<VidWidget *> vidwdglist = getVideoWindowsList();
for(int iii = 0; iii < vidwdglist.size(); ++iii)
	{
	VidWidget *vidwdg = vidwdglist.at(iii);
	if(!vidwdg) continue;

	switch(vidwdglist.size())
		{
		case 1:	layout->addWidget(vidwdg, 0, 0, 1, 1); break;
		case 2: {
			int row = 0, col = 0;
			switch(iii)
				{
				case 0: row = 1; col = 0; break;
				case 1: row = 1; col = 1; break;
			}
			layout->setRowStretch(0, 1);
			layout->addWidget(vidwdg, row, col, 1, 1);
			layout->setRowStretch(1, 2);
			layout->setRowStretch(2, 1);
		} break;
		case 3:
		case 4: {
			int row = 0, col = 0;
			switch(iii)
				{
				case 0: row = 0; col = 0; break;
				case 1: row = 0; col = 1; break;
				case 2: row = 1; col = 0; break;
				case 3: row = 1; col = 1; break;
			}
			layout->addWidget(vidwdg, row, col, 1, 1);
		} break;
		case 5:
		case 6: {
			int row = 0, col = 0, rowspan = 1, colspan = 1;
			switch(iii)
				{
				case 0: row = 0; col = 0; rowspan = 2; colspan = 2; break;
				case 1: row = 0; col = 2; break;
				case 2: row = 1; col = 2; break;
				case 3: row = 2; col = 0; break;
				case 4: row = 2; col = 1; break;
				case 5: row = 2; col = 2; break;
			}
			layout->addWidget(vidwdg, row, col, rowspan, colspan);
		} break;
		case 7:
		case 8:
		case 9: {
			int row = 0, col = 0;
			switch(iii)
				{
				case 0: row = 0; col = 0; break;
				case 1: row = 0; col = 1; break;
				case 2: row = 0; col = 2; break;
				case 3: row = 1; col = 0; break;
				case 4: row = 1; col = 1; break;
				case 5: row = 1; col = 2; break;
				case 6: row = 2; col = 0; break;
				case 7: row = 2; col = 1; break;
				case 8: row = 2; col = 2; break;
			}
			layout->addWidget(vidwdg, row, col, 1, 1);
		} break;
		case 10:
		case 11:
		case 12: {
			int row = 0, col = 0;
			switch(iii)
				{
				case 0: row = 1; col = 0; break;
				case 1: row = 1; col = 1; break;
				case 2: row = 1; col = 2; break;
				case 3: row = 1; col = 3; break;
				case 4: row = 2; col = 0; break;
				case 5: row = 2; col = 1; break;
				case 6: row = 2; col = 2; break;
				case 7: row = 2; col = 3; break;
				case 8: row = 3; col = 0; break;
				case 9: row = 3; col = 1; break;
				case 10: row = 3; col = 2; break;
				case 11: row = 3; col = 3; break;
			}
			layout->setRowStretch(0, 1);
			layout->addWidget(vidwdg, row, col, 1, 1);
			layout->setRowStretch(1, 2);
			layout->setRowStretch(2, 2);
			layout->setRowStretch(3, 2);
			layout->setRowStretch(4, 1);
		} break;
		case 13:
		case 14:
		case 15:
		case 16: {
			int row = 0, col = 0;
			switch(iii)
				{
				case 0: row = 0; col = 0; break;
				case 1: row = 0; col = 1; break;
				case 2: row = 0; col = 2; break;
				case 3: row = 0; col = 3; break;
				case 4: row = 1; col = 0; break;
				case 5: row = 1; col = 1; break;
				case 6: row = 1; col = 2; break;
				case 7: row = 1; col = 3; break;
				case 8: row = 2; col = 0; break;
				case 9: row = 2; col = 1; break;
				case 10: row = 2; col = 2; break;
				case 11: row = 2; col = 3; break;
				case 12: row = 3; col = 0; break;
				case 13: row = 3; col = 1; break;
				case 14: row = 3; col = 2; break;
				case 15: row = 3; col = 3; break;
			}
			layout->addWidget(vidwdg, row, col, 1, 1);
		} break;
	}
}

//Показать все видеоокна.
for(int i = 0; i < vidwdglist.size(); ++i)
	{vidwdglist.at(i)->show();}
}


//===========================================================================//
//=Заголовок: void MainWindow::hideVideoWindows() - private
//=Параметры:
//=Описание: Функция скрытия видеоокон.
//===========================================================================//
void MainWindow::hideVideoWindows()
{
QList<VidWidget *> vidwdglist = getVideoWindowsList();
for(int i = 0; i < vidwdglist.size(); ++i)
	{vidwdglist.at(i)->hide();}
}


//===========================================================================//
//=Заголовок: void MainWindow::showLogo() - private
//=Параметры:
//=Описание: Функция вывода логотипа.
//===========================================================================//
void MainWindow::showLogo()
{
if(!logoLabel)
	{
	logoLabel = new QLabel(mainWidget);
	logoLabel->setPixmap(QPixmap(":/images/chubr.png"));
}

QLayout *oldlayout = mainWidget->layout();
if(oldlayout) delete oldlayout;

QGridLayout *layout = new QGridLayout(mainWidget);
layout->setMargin(1); layout->setSpacing(1);
layout->addWidget(logoLabel, 1, 1, 1, 1);
layout->setRowStretch(0, 1);
layout->setRowStretch(2, 1);
layout->setColumnStretch(0, 1);
layout->setColumnStretch(2, 1);

QString msg = tr("Нажмите <Ctrl> + <A> для добавления устройств в программу. Клавиша <Esc> - выход");
statusBar()->showMessage(msg, 20000);
}


//===========================================================================//
//=Заголовок: void MainWindow::slotDevicesConnect() - private slots
//=Параметры:
//=Описание: Слот подключения новых устройств.
//===========================================================================//
void MainWindow::slotDevicesConnect(const QList<VidDevice *> &viddevlist)
{
//Скрыть видеоокна.
hideVideoWindows();

//Получить тип видеоокна из файла настроек приложения.
QSettings settings;
QVariant vidwintype = settings.value("VIDWINTYPE", "XVIDEO");

//Создать новые видеоокна.
for(int i = 0; i < viddevlist.size(); ++i)
	{
	if(getVideoWindowsList().size() >= 16) break;

	VidDevice *viddev = viddevlist.at(i);
	if(!viddev) continue;

	VidWidget *vidwdg = NULL;
	if(vidwintype.toString() == "OPENGL")
		{vidwdg = new OpenGLWidget(viddev->getIndex(), mainWidget);}
	else if(vidwintype.toString() == "XVIDEO")
		{vidwdg = new XVideoWidget(viddev->getIndex(), mainWidget);}
	else continue;

	connect(vidwdg, SIGNAL(information(const QString &)), this, SLOT(slotInformationShow(const QString &)));
	connect(vidwdg, SIGNAL(warning(const QString &)), this, SLOT(slotWarningShow(const QString &)));
	connect(vidwdg, SIGNAL(critical(const QString &)), this, SLOT(slotCriticalShow(const QString &)));
	vidwdg->setDeviceManager(devManager);
	vidwdg->connectVideoDevice(viddev);
}

//Показать видеоокна.
if(!getVideoWindowsList().isEmpty()) showVideoWindows();
else showLogo();
}


//===========================================================================//
//=Заголовок: void MainWindow::slotDevicesDisconnect() - private slots
//=Параметры:
//=Описание: Слот отключения устройств.
//===========================================================================//
void MainWindow::slotDevicesDisconnect(const QList<int> &devindexlist)
{
//Скрыть видеоокна.
hideVideoWindows();

int devindex = -1;
foreach(devindex, devindexlist)
	{
	QList<VidWidget *> vidwdglist = getVideoWindowsList();
	for(int i = 0; i < vidwdglist.size(); ++i)
		{
		VidWidget *vidwdg = vidwdglist.at(i);
		if(!vidwdg) continue;

		if(devindex == vidwdg->getDeviceIndex())
			{delete vidwdg; break;}
	}
}

//Показать видеоокна.
if(!getVideoWindowsList().isEmpty()) showVideoWindows();
else showLogo();
}


//===========================================================================//
//=Заголовок: void MainWindow::slotVidWinTypeChange() - private slots
//=Параметры:
//=Описание: Слот смены типа видеоокон.
//===========================================================================//
void MainWindow::slotVidWinTypeChange(const QString &vidwintype)
{
//Скрыть видеоокна.
hideVideoWindows();

//Получить список видеоокон.
QList<VidWidget *> vidwdglist = getVideoWindowsList();

//Сформировать временный список устройств,
//для которых производится замена видеоокон.
QList<VidDevice *> viddevlist;
while(!vidwdglist.isEmpty())
	{
	VidWidget *vidwdg = vidwdglist.takeFirst();
	if(!vidwdg) continue;

	VidDevice *viddev = vidwdg->getDevice();
	if(!viddev) continue;

	//Обозначить отключение видеоокна от устройства.
	vidwdg->connectVideoDevice(NULL);

	//Удалить видеоокно.
	delete vidwdg;

	//Добавить устройство во временный список.
	viddevlist.append(viddev);
}

//Сохранить новый тип видеоокна в файл настроек приложения.
QSettings settings;
settings.setValue("VIDWINTYPE", vidwintype);

//Создать новые видеоокна.
for(int i = 0; i < viddevlist.size(); ++i)
	{
	VidDevice *viddev = viddevlist.at(i);
	if(!viddev) continue;

	VidWidget *vidwdg = NULL;
	if(vidwintype == "OPENGL")
		{vidwdg = new OpenGLWidget(viddev->getIndex(), mainWidget);}
	else if(vidwintype == "XVIDEO")
		{vidwdg = new XVideoWidget(viddev->getIndex(), mainWidget);}
	else continue;

	connect(vidwdg, SIGNAL(information(const QString &)), this, SLOT(slotInformationShow(const QString &)));
	connect(vidwdg, SIGNAL(warning(const QString &)), this, SLOT(slotWarningShow(const QString &)));
	connect(vidwdg, SIGNAL(critical(const QString &)), this, SLOT(slotCriticalShow(const QString &)));
	vidwdg->setDeviceManager(devManager);
	vidwdg->connectVideoDevice(viddev);
}

//Показать видеоокна.
if(!getVideoWindowsList().isEmpty()) showVideoWindows();
else showLogo();
}


//===========================================================================//
//=Заголовок: void MainWindow::slotInformationShow() - private slots
//=Параметры:
//=Описание: Слот вывода сообщений.
//===========================================================================//
void MainWindow::slotInformationShow(const QString &msg)
{if(!getVideoWindowsList().isEmpty()) statusBar()->showMessage(msg, 1000);}


//===========================================================================//
//=Заголовок: void MainWindow::slotWarningShow() - private slots
//=Параметры:
//=Описание: Слот вывода предупреждений.
//===========================================================================//
void MainWindow::slotWarningShow(const QString &msg)
{QMessageBox::warning(this, "Предупреждение", msg);}


//===========================================================================//
//=Заголовок: void MainWindow::slotCriticalShow() - private slots
//=Параметры:
//=Описание: Слот вывода ошибок.
//===========================================================================//
void MainWindow::slotCriticalShow(const QString &msg)
{QMessageBox::critical(this, "Ошибка", msg);}
